using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace socks5_demo
{
    internal class Program
    {
        static ushort port = 8300;
        static void Main(string[] args)
        {
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, port);
            Socket socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            socket.Bind(ep);
            socket.Listen(int.MaxValue);
            while (true)
            {
                Socket client = socket.Accept();
                ClientReceive(client);
            }
        }

        /// <summary>
        /// 接收，处理客户端的数据
        /// </summary>
        /// <param name="client"></param>
        static void ClientReceive(Socket client)
        {
            Task.Run(() =>
            {
                UserToken token = new UserToken
                {
                    ClientSocket = client,
                    ClientBuffer = new byte[8 * 1024],
                    Step = Socks5Step.Request
                };
                while (true)
                {
                    try
                    {
                        int length = client.Receive(token.ClientBuffer);
                        token.ClientData = token.ClientBuffer.AsMemory(0, length);
                        switch (token.Step)
                        {
                            case Socks5Step.Request:
                                Request(token);
                                break;
                            case Socks5Step.Auth:
                                Auth(token);
                                break;
                            case Socks5Step.Command:
                                Command(token);
                                break;
                            case Socks5Step.Forward:
                                Forward(token);
                                break;
                            default:
                                break;
                        }
                    }
                    catch (Exception)
                    {
                    }
                }
            });
        }
        /// <summary>
        /// 处理请求
        /// </summary>
        /// <param name="token"></param>
        static void Request(UserToken token)
        {
            token.ClientSocket.Send(new byte[] { 0x05, 0x00 });
            token.Step = Socks5Step.Command;
        }
        /// <summary>
        /// 处理认证
        /// </summary>
        /// <param name="token"></param>
        static void Auth(UserToken token)
        {

        }
        /// <summary>
        /// 处理命令
        /// </summary>
        /// <param name="token"></param>
        static void Command(UserToken token)
        {
            var data = token.ClientData.Slice(3);
            Socks5AddressType addressType = (Socks5AddressType)data.Span[0];
            Socks5CommandType socks5CommandType = (Socks5CommandType)token.ClientData.Span[1];

            IPAddress ip = IPAddress.Any;
            ushort port = 0;
            switch (addressType)
            {
                case Socks5AddressType.IPV4:
                    {
                        ip = new IPAddress(data.Span.Slice(1, 4));
                        port = BinaryPrimitives.ReadUInt16BigEndian(data.Span.Slice(5, 2));
                    }
                    break;
                case Socks5AddressType.Domain:
                    {
                        byte length = data.Span[1];
                        ip = Dns.GetHostEntry(Encoding.UTF8.GetString(data.Span.Slice(2, length))).AddressList[0];
                        port = BinaryPrimitives.ReadUInt16BigEndian(data.Span.Slice(2 + length, 2));
                    }
                    break;
                case Socks5AddressType.IPV6:
                    {
                        ip = new IPAddress(data.Span.Slice(1, 16));
                        port = BinaryPrimitives.ReadUInt16BigEndian(data.Span.Slice(17, 2));
                    }
                    break;
            }

            //把步骤改变到转发阶段
            token.Step = Socks5Step.Forward;

            byte[] serverPort = BitConverter.GetBytes(Program.port);
            if (socks5CommandType == Socks5CommandType.Connect)
            {
                //返回连接成功
                IPEndPoint targetEP = new IPEndPoint(ip, port);
                token.TargetSocket = new Socket(targetEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                token.TargetSocket.Connect(targetEP);
                token.TargetBuffer = new byte[8 * 1024];
                IAsyncResult result = token.TargetSocket.BeginReceive(token.TargetBuffer, 0, token.TargetBuffer.Length, SocketFlags.None, TargetReceiveCallback, token);
                
                token.ClientSocket.Send(new byte[] { 0x05, 0x0, 0, (byte)Socks5AddressType.IPV4, 0, 0, 0, 0, serverPort[1], serverPort[0] });
            }
            else
            {
                //返回服务器故障
                token.ClientSocket.Send(new byte[] { 0x05, 0x01, 0, (byte)Socks5AddressType.IPV4, 0, 0, 0, 0, serverPort[1], serverPort[0] });
            }
        }
        /// <summary>
        /// 接收目标端数据的回调
        /// </summary>
        /// <param name="result"></param>
        static void TargetReceiveCallback(IAsyncResult result)
        {
            try
            {
                UserToken token = result.AsyncState as UserToken;
                int length = token.TargetSocket.EndReceive(result);
                token.ClientSocket.Send(token.TargetBuffer, length, SocketFlags.None);
                //继续目标端的数据
                token.TargetSocket.BeginReceive(token.TargetBuffer, 0, token.TargetBuffer.Length, SocketFlags.None, TargetReceiveCallback, token);
            }
            catch (Exception)
            {
            }
        }

        /// <summary>
        /// 处理转发
        /// </summary>
        /// <param name="token"></param>
        static void Forward(UserToken token)
        {
            try
            {
                token.TargetSocket.Send(token.ClientData.Span, SocketFlags.None);
            }
            catch (Exception)
            {
            }
        }
    }

    public class UserToken
    {
        public Socket ClientSocket { get; set; }
        public byte[] ClientBuffer { get; set; }
        public Memory<byte> ClientData { get; set; }
        public Socks5Step Step { get; set; }

        public Socket TargetSocket { get; set; }
        public byte[] TargetBuffer { get; set; }
    }

    public enum Socks5Step : byte
    {
        Request = 0,
        Auth = 1,
        Command = 2,
        Forward = 3
    }
    public enum Socks5AddressType : byte
    {
        IPV4 = 1,
        Domain = 3,
        IPV6 = 4
    }
    public enum Socks5CommandType : byte
    {
        Connect = 1,
        Bind = 2,
        Udp = 3
    }
}